perm filename CL1[COM,LSP] blob sn#813019 filedate 1986-03-19 generic text, type T, neo UTF8
Part 1 of 2

-*- Mode: LISP -*-		Error Proposal #5 by KMP 3/15/86, Page 1

Error System Proposal #5 by Kent M. Pitman (KMP@Symbolics), 15-Mar-86.

This draft incorporates numerous suggestions made by David A. Moon 
(Moon@Symbolics) and Richard Mlynarik (MLY@MIT-MC). Not all of their
suggestions made it into this proposal however, either because of
some technical disagreement or limitations of time, so it shouldn't
be seen as contradictory if they criticize aspects of the proposal.


				 TERMINOLOGY
				 -----------

A ``condition'' is a kind of object which is created when an exceptional
situation arises in order to represent the relevant features of that
situation.

Once a condition is created, it is common to ``signal'' it. When a condition
is signalled, a set of handlers are tried in some pre-defined order until one
decides to ``handle'' the condition or until no more handlers are found. A
condition is said to have been ``handled'' if a handler performs a non-local 
transfer of control to exit the signalling process.

Although such transfers of control may be done directly using traditional Lisp
mechanisms such as CATCH and THROW, BLOCK and RETURN, or TAGBODY and GO, the
condition system also provides a way to ``proceed'' from a condition which is
more structured. Among other things, the use of these structured primitives
for proceeding allow better a more integrated relationship between the user
program and the interactive debugger.

It is not necessary that all conditions be handled. Some conditions are 
trivial enough that a failure to handle them may be disregarded. Others,
which we will call ``serious conditions'' must be handled in order to assure
correct program behavior. If a serious condition is signalled but no handler
is found, the debugger will be entered so that the user may interactively 
specify how to proceed.

Serious conditions which result from incorrect programs or data are
called ``errors.'' Not all serious conditions are errors, however. Storage 
conditions are examples of serious conditions that are not errors. 
For example, the control stack may legitimately overflow without a program
being in error. Even though a stack overflow is not necessarily a program
error, it is serious enough to warrant entry to the debugger if the 
condition goes unhandled.

Some types of conditions are predefined by the system. All types of conditions
are subtypes of CONDITION. That is, (TYPEP C 'CONDITION) is true iff C is a
condition. 

Systems with non-hierarchical type systems are allowed to use such
non-hierarchical types in a CL-visible way as long as (TYPEP c 'CONDITION) is
true for such conditions.  No mechanism is currently provided at this time for
creating condition types that directly exploit non-hierarchical inheritance.

The only CL-provided mechanism for defining new condition types is DEFINE-CONDITION.
The only CL-provided mechanism for instantiating a condition is MAKE-CONDITION.


				Error Proposal #5 by KMP 3/15/86, Page 2

When a condition object is created, the most common operation to be performed
upon it is to ``signal'' it (although there may be applications in which this
does not happen, or does not happen immediately).

When a condition is signalled, the system tries locate the most appropriate 
handler for the condition and invoke that handler. Handlers are located according
to the following rules:

  * Check for locally defined (ie, bound) handlers.
  * If no appropriate bound handler is found, check for the default handler
    first of the signalled type and then of each of its superiors.

If an appropriate handler is found, it is called. In some circumstances
(to be described later), the handler may ``decline'' by simply returning
without performing a non-local transfer of control. In such cases, the 
search for an appropriate handler is picked up where it left off, as if
the called handler had never been present.

If no bound handler or default handler is found, or if all handlers which were
found decline, SIGNAL returns the condition which was signalled.

When a condition is signalled, handlers are searched for in the dynamic 
environment of the signaller. Handlers can be established within a dynamic 
context by use of CONDITION-BIND. 

A ``handler'' is a function of one argument, the condition to be handled. The 
handler may inspect the object (using primitives described in another section)
to be sure it is `interested' in handling the condition.  After inspecting the
condition, the handler must take one of the following actions:

  * It may decline to handle the condition, by simply returning. When this 
    happened, the returned values are ignored and the effect is the same as
    if the handler had been invisible to the mechanism seeking to find a 
    handler. The next handler in line will be tried, or if no such handler, 
    the default action for the given condition will be taken. A default handler
    may also decline, in which case the condition will go unhandled.

  * It may perform some non-local transfer of control using GO, RETURN, THROW,
    ABORT, or INVOKE-PROCEED-CASE.

  * It may signal another condition.

  * It may invoke the interactive debugger.

In some cases, it may be useful to ``report'' a condition or a proceed case 
to a user or a log file of some sort. When the printer is invoked on a condition
or proceed case while *PRINT-ESCAPE* is NIL, the report-function for that object
is invoked. In particular, this means that an expression like 
 (FORMAT T "~A" CONDITION)
will invoke CONDITION's report function. Because of this, no special function 
is provided for invoking the report function of a condition or a proceed case.

				Error Proposal #5 by KMP 3/15/86, Page 3


		  PROGRAM INTERFACE TO THE CONDITION SYSTEM
		  -----------------------------------------

ERROR datum &rest arguments				[Function]

  Invokes the signal facility on a condition. If the condition is not handled,
  (DEBUG condition) is done.

  If DATUM is a condition, then that condition is used directly. 
  In this case, it is an error for ARGUMENTS to be non-NIL.

  If DATUM a condition type, then the condition used is the result
  of doing (APPLY #'MAKE-CONDITION datum arguments).

  If DATUM is a string, then the condition used is the result of 
  doing (MAKE-CONDITION 'SIMPLE-ERROR 
	  :FORMAT-STRING    datum
	  :FORMAT-ARGUMENTS arguments).

CERROR proceed-format-string datum &rest arguments	[Function]

  Invokes the error facility on a condition. If the condition is not handled,
  (DEBUG condition). While signalling is going on, and while in the 
  debugger if it is reached, it is possible to proceed this error using the
  proceed function PROCEED.

  If DATUM is a condition, then that condition is used directly. 
  In this case, ARGUMENTS will be used only with the PROCEED-FORMAT-STRING
  and will not be used to initialized DATUM.

  If DATUM a condition type, then the condition used is the result
  of doing (APPLY #'MAKE-CONDITION datum arguments).

  If DATUM is a string, then the condition used is the result of 
  doing (MAKE-CONDITION 'SIMPLE-ERROR 
	  :FORMAT-STRING datum
	  :FORMAT-ARGUMENTS arguments).

  The PROCEED-FORMAT-STRING must be a string. Note that if DATUM is not a 
  string, then the format arguments used by the PROCEED-FORMAT-STRING will
  still be the ARGUMENTS (in the keyword format as specified). In this case,
  some care may be necessary to set up the PROCEED-FORMAT-STRING correctly. 
  The format op ~* may be particularly useful in this situation.

  The value returned by CERROR is the condition which was signalled.

				Error Proposal #5 by KMP 3/15/86, Page 4

BREAK datum &rest arguments				[Function]

  Directly enters the debugger with a condition without trying to invoke
  the signal facility. Executing the function PROCEED while in the debugger
  will cause a return from the BREAK. 

  If DATUM is a condition, then that condition is used directly. 
  In this case, it is an error for ARGUMENTS to be non-NIL.

  If DATUM is a condition type, then the condition used is the result
  of doing (APPLY #'MAKE-CONDITION datum arguments).

  If DATUM is a string, then the condition used is the result of 
  doing (MAKE-CONDITION 'SIMPLE-BREAK
	  :FORMAT-STRING datum
	  :FORMAT-ARGUMENTS arguments).

  If the break is proceeded, the value returned is the condition that was used.

  Implementation Note: BREAK could be defined by:

	(DEFUN BREAK (DATUM &REST ARGUMENTS)
	  (PROCEED-CASE (DEBUG
			  (COND ((TYPEP DATUM 'CONDITION) DATUM)
				((SYMBOLP DATUM) ;roughly, (SUBTYPEP DATUM 'CONDITION)
				 (APPLY #'MAKE-CONDITION DATUM ARGUMENTS))
				((STRINGP DATUM)
				 (MAKE-CONDITION 'SIMPLE-BREAK
				   :FORMAT-STRING DATUM
				   :FORMAT-ARGUMENTS ARGUMENTS))
				(T
				 (ERROR "Bad argument to BREAK: ~S" DATUM))))
	    (PROCEED (CONDITION) 
		:TEST (LAMBDA (IGNORE) T)
	        :REPORT "Return from BREAK."
	      CONDITION)))

  See Footnote: {TRUE}

				Error Proposal #5 by KMP 3/15/86, Page 5


WARN datum &rest arguments				[Function]

  Invokes the signal facility on a condition. If the condition is not handled,
  then the text of the warning is output to error output (with possible
  implementation-specific extras such as moving to a fresh line before and
  after the display of the warning, or supplying some introductory that might 
  mention the name of the function which called WARN).  If *BREAK-ON-WARNINGS* 
  is true, then in addition to printing the warning, the debugger is entered.
  In this case, WARN returns only if PROCEED is done from the debugger. The
  value returned by WARN is the condition which was signalled.

  If DATUM is a condition, then that condition is used directly. 
  In this case, it is an error for ARGUMENTS to be non-NIL.

  If DATUM is a condition type, then the condition used is the result
  of doing (APPLY #'MAKE-CONDITION datum arguments).

  If DATUM is a string, then the condition used is the result of 
  doing (MAKE-CONDITION 'SIMPLE-WARNING
	  :FORMAT-STRING datum
	  :FORMAT-ARGUMENTS arguments).


*BREAK-ON-WARNINGS*					[Variable]

  {As described in CLtL}

				Error Proposal #5 by KMP 3/15/86, Page 6

DEFINE-CONDITION name parent-type [keyword value]* &rest slots
							[Special Form]

  Defines a new condition type with the given NAME, which is a 
  subtype of the given PARENT-TYPE. Except as otherwise noted,
  the arguments are not evaluated.

  The valid KEYWORD/VALUE pairs are:

   :CONC-NAME symbol-or-string

     As in DEFSTRUCT, this sets up automatic prefixing of the names 
     of slot accessors. Also as in DESTRUCT, the default behavior is to use
     the name of the new type, NAME, followed by a hyphen.

   :REPORT-FUNCTION expression

     EXPRESSION should be a suitable argument to the FUNCTION special form.
     It designates a function of two arguments, a condition and a stream, 
     which prints the condition to the stream when *PRINT-ESCAPE* is NIL.

   :REPORT form

     A short form of :REPORT-FUNCTION to cover two common cases.
     If form is a constant string, this is the same as
	:REPORT-FUNCTION (LAMBDA (IGNORE STREAM) (WRITE-STRING form STREAM))
     Otherwise, this is the same as
	:REPORT-FUNCTION (LAMBDA (CONDITION *STANDARD-OUTPUT*) form)
     In this latter case, the form describes how to print objects of the type
     being defined. The form should do output to standard output. The condition
     being printed will be the value of the variable CONDITION.

   :HANDLE form

     An expression to be used as the body of a default handler for this
     condition type. While executing form, the variable CONDITION will
     be bound to the condition being handled.

  It is an error to specify both :REPORT-FUNCTION and :REPORT in the
  same DEFINE-CONDITION. If neither :REPORT-FUNCTION nor :REPORT is specified,
  information about how to print this type of condition will be inherited 
  from the PARENT-TYPE.

  SLOTS is a list of slot spec, and specifies slots to be used by the
  given type. In addition to those specified, the slots of the PARENT-TYPE 
  are also available.
 
  A slot spec is either the name of a slot, or a list, the car of
  which is the slot name and the cadr is a form which will can evaluated
  by MAKE-CONDITION to produce a default value when an explicit value 
  is not provided. If no slot default is specified, NIL is assumed.
 
  If a slot name is specified which is the same as the name in some
  type of which this type is a subtype, only one shared slot is created
  in the condition object, but the specified default overrides any 
  default which might otherwise have been inherited from a parent type.
 
				Error Proposal #5 by KMP 3/15/86, Page 7

  MAKE-CONDITION will accept keywords with the printname of any of 
  the designated slots, and will initialize the corresponding slots in
  conditions it creates.
 
  Accessors are created according to the same rules as used by DEFSTRUCT.
  For example:
	    (DEFINE-CONDITION BAD-FOOD-COLOR FOOD-LOSSAGE
		:REPORT (FORMAT T "The food ~A was ~A."
				(BAD-FOOD-COLOR-FOOD  CONDITION)
				(BAD-FOOD-COLOR-COLOR CONDITION))
	      FOOD 
	      COLOR)
  defines an error of type BAD-FOOD-COLOR which inherits from the 
  FOOD-LOSSAGE condition type. The new type has slots FOOD and COLOR
  so that MAKE-CONDITION will accept :FOOD and :COLOR keywords and 
  accessors BAD-FOOD-COLOR-FOOD and BAD-FOOD-COLOR-COLOR will apply
  to objects of this type.

No functions are provided for directly accessing the printer or the default
handler for a condition since it is not believed that this will be very 
useful in portable code (at least until an object system is better defined).

The printer for a condition will be implicitly called any time a condition
is printed with *PRINT-ESCAPE* being NIL. Hence, (PRINC condition) or
(FORMAT T "~A" condition) are possible ways of invoking the condition's 
printer.

The default handler will be implicitly called during the signalling process
and never has any reason to be manually invoked.

Here are some examples of creating conditions. These forms define a condition
called MACHINE-ERROR which inherits from ERROR: 
 
   (DEFINE-CONDITION MACHINE-ERROR ERROR
       :REPORT (FORMAT T "There is a problem with ~A."
		       (MACHINE-ERROR-MACHINE-NAME CONDITION))
     MACHINE-NAME)
 
This defines a new error condition (a subtype of MACHINE-ERROR) for use when
machines are not available: 
 
   (DEFINE-CONDITION MACHINE-NOT-AVAILABLE-ERROR MACHINE-ERROR
       :REPORT (FORMAT T "The machine ~A is not available."
		       (MACHINE-ERROR-MACHINE-NAME CONDITION)))
 
This defines a still more specific condition, built upon
MACHINE-NOT-AVAILABLE-ERROR, which provides a default for MACHINE-NAME but
which does not provide any new slots: 
 
   (DEFINE-CONDITION MY-FAVORITE-MACHINE-NOT-AVAILABLE-ERROR
		     MACHINE-NOT-AVAILABLE-ERROR
     (MACHINE-NAME "MIT-MC.ARPA"))

This gives the MACHINE-NAME slot a default initialization. Since no :REPORT
clause was given, the information supplied in the definition of
MACHINE-NOT-AVAILABLE-ERROR will be used if a condition of this type is
printed while *PRINT-ESCAPE* is NIL.

				Error Proposal #5 by KMP 3/15/86, Page 8

MAKE-CONDITION type &rest slot-initializations		[Function]

   Calls the appropriate constructor function for the given type, passing
   along the given slot initializations to the constructor, and returning
   an instantiated condition.

   The SLOT-INITIALIZATIONS are given in alternating keyword/value pairs.
   eg, (MAKE-CONDITION 'BAD-FOOD-COLOR :FOOD MY-FOOD :COLOR MY-COLOR)

   Design Note: If either DEFSTRUCT or some other type system adopted
   later by CL ever provides a MAKE-INSTANCE primitive, MAKE-CONDITION 
   would no longer be necessary.

SIGNAL datum &rest arguments				[Function]

  Invokes the signal facility on a condition. If the condition is not handled,
  SIGNAL returns the condition object it was attempting to handle.

  If DATUM is a condition, then that condition is used directly. 
  In this case, it is an error for ARGUMENTS to be non-NIL.

  If DATUM is a condition type, then the condition used is the result
  of doing (APPLY #'MAKE-CONDITION datum arguments).

  If DATUM is a string, then the condition used is the result of 
  doing (MAKE-CONDITION 'SIMPLE-CONDITION 
	  :FORMAT-STRING datum
	  :FORMAT-ARGUMENTS arguments).

CONDITION-BIND bindings &rest forms			[Special Form]

  Executes body in a dynamic context where the giving local handler bindings 
  are in effect. The BINDINGS must take the form (type handler).

  TYPE may be a type or a list of types.

  HANDLER should evaluate to a function to be used to handle conditions of the
  given type(s) during execution of the FORMS.

DEBUG condition			 			[Function]

  Enters the debugger with a given condition. 

  This function  will never directly return. Return can occur only by a
  special transfer of control, such as to a PROCEED-CASE or CATCH-ABORT.


				Error Proposal #5 by KMP 3/15/86, Page 9

COMPUTE-PROCEED-CASES condition				[Function]

  Uses the dynamic state of the program to compute a list of ``proceed 
  cases'' which may be used with the given CONDITION. 

  Each ``proceed case'' represents a point in the current dynamic state of
  the program to which control may be transferred. Implementations are free
  to implement these objects in whatever manner is most convenient; the 
  objects need have only dynamic extent. The only operations which 
  Common Lisp defines for such objects are PROCEED-CASE-NAME, 
  FIND-PROCEED-CASE, INVOKE-PROCEED-CASE, PRINC and PRINT, the 
  identification of an object as a proceed case using 
  (TYPEP x 'PROCEED-CASE), and standard lisp operations which work for 
  all objects such as EQ, EQL, DESCRIBE, etc.

  The list which results from a call to COMPUTE-PROCEED-CASES is ordered 
  so that the innermost (ie, more-recently established) proceed cases 
  are nearer the head of the list.

  Note, too, that COMPUTE-PROCEED-CASES returns all valid proceed cases for
  CONDITION, even if some of them have the same name as others and therefore
  would not be found by FIND-PROCEED-CASE.

  Implementations are permitted, but not required, to return different 
  (ie, non-EQ) lists from repeated calls to COMPUTE-PROCEED-CASES while 
  in the same lexical/dynamic environment. It is an error to modify the
  list which is returned by COMPUTE-PROCEED-CASES.

				Error Proposal #5 by KMP 3/15/86, Page 10

When a condition is signalled, a facility is available for use by handlers to
non-locally transfer control to an outer dynamic contour of the program. The
form which creates contours which may be returned to is called PROCEED-CASE.
The function which transfers control to a PROCEED-CASE clause is called
INVOKE-PROCEED-CASE.

PROCEED-CASE form &rest clauses			[Special Form]

  The form is evaluated in a dynamic context where the clauses have 
  special meanings as points to which control may be transferred in the 
  event that a condition is signalled. If form runs to completion and 
  returns any values, all values returned by the form are simply returned
  by the PROCEED-CASE form. If a condition is signalled while form is 
  running, a handler may tranfer control to one of the clauses. If a
  transfer occurs, the forms in the body of that clause will be evaluated 
  and any values returned by the last such form will be returned by the 
  PROCEED-CASE form.

  A PROCEED-CASE clause has the form:

	(proceed-function-name arglist [keyword value]* [body-form]*)

  The PROCEED-FUNCTION-NAME may be NIL or the name of a defined proceed 
  function.

  The ARGLIST is a list of variables to be bound during the execution of 
  the body-forms. The first variable will be the condition, and the remaining
  will be additional data provided by the proceed function. By special 
  exemption, the arglist may be () if you don't care about any of the 
  arguments; otherwise, the argument list must be compatible with the 
  arguments as passed by INVOKE-PROCEED-CASE. If a PROCEED-FUNCTION-NAME was
  supplied, then the arguments in the arglist need not be optional, since 
  the proceed-function will have taken care of filling in all optional
  arguments and a fixed number of arguments will always be passed to 
  INVOKE-PROCEED-CASE.

  The valid KEYWORD/VALUE pairs are:

   :TEST function

     A function of one argument, the condition, which must return true for
     this case to be "visible" to handlers. The function should be in a form
     which is acceptable as an argument to the FUNCTION special form.

   :CONDITION type

      Shorthand for the common special case of :TEST in which the user is
      doing a test of the given TYPE of condition being signalled to 
      determine its visibility. The following two pairs are equivalent:
	:CONDITION foo
	:TEST (LAMBDA (C) (TYPEP C 'foo))
				
				Error Proposal #5 by KMP 3/15/86, Page 11

   :REPORT-FUNCTION exp

     The EXP must be an appropriate argument to the FUNCTION special form,
     and should designate a function of two arguments, a proceed case and
     a stream, which summarizes the action that this proceed case will take.

   :REPORT form

     This is a shorthand for two important special cases of :REPORT-FUNCTION.
     If FORM is a constant string, then this is the same as:
	:REPORT-FUNCTION (LAMBDA (IGNORE STREAM)
			   (WRITE-STRING form STREAM))
     Otherwise, this is the same as
	:REPORT-FUNCTION (LAMBDA (CONDITION *STANDARD-OUTPUT*)
			   form)
     In the latter case, form must do output to standard output, summarizing
     the action that this proceed case will take.

  Only one of :TEST or :CONDITION may be specified. 
  Only one of :REPORT or :REPORT-FUNCTION may be specified.

  If a named proceed function has a default for any of :TEST or :CONDITION 
  and the proceed case specifies any of :TEST or :CONDITION,
  then the information supplied in the proceed case is the only information 
  considered. Similarly, if :REPORT or :REPORT-FUNCTION is specified in 
  the proceed case, then only that information is considered, and any :REPORT 
  or :REPORT-FUNCTION specified as a default for the named proceed function 
  is not used.

  If a named proceed function is used but no report information is supplied, 
  the name of the proceed function is used to generate the default help 
  information. It is an error if no named proceed case is used and no report
  information is provided; implementations are encouraged to flag this error 
  at the earliest convenient time (eg, compilation time).

  When *PRINT-ESCAPE* is NIL, the printer will use report information for
  a proceed case. eg, a debugger might announce the action of typing
  Control-Z by doing:
      (FORMAT T "~&Control-Z: ~A~%" SOME-PROCEED-CASE)
  which would then display as something like:
      Control-Z: Return to command level.

				Error Proposal #5 by KMP 3/15/86, Page 12

  Examples:

	(PROCEED-CASE (A-RANDOM-COMPUTATION)
	  (NEW-FUNCTION (IGNORE NEW-FUNCTION)
	    (SETQ FUNCTION NEW-FUNCTION)))

        (PROCEED-CASE (A-RANDOM-COMPUTATION)
	  (NIL (IGNORE &OPTIONAL
			 (NEW-FUNCTION (READ-TYPED-OBJECT 'FUNCTION "Function: ")))
	       :REPORT "Use a different function."
	       :CONDITION UNDEFINED-FUNCTION
	    (SETQ FUNCTION NEW-FUNCTION)))

        (PROCEED-CASE (A-COMMAND-LOOP)
	  (RETURN-FROM-COMMAND-LEVEL ()
              :REPORT (FORMAT T "Return from command level ~D." LEVEL)
	    NIL))

	(LOOP 
          (PROCEED-CASE (ANOTHER-RANDOM-COMPUTATION)
	    (PROCEED ())))

  Assuming that NEW-FUNCTION is defined as a proceed function with defaults:
    :REPORT "Use a different function."
    :CONDITION UNDEFINED-FUNCTION
  then the first and second example are equivalent from the point of view of 
  someone using the interactive debugger, but differ in one important aspect 
  for non-interactive handling. If a handler `knows about' proceed function 
  names, as in: (IF (FIND-PROCEED-CASE 'NEW-FUNCTION CONDITION)
	            (NEW-FUNCTION CONDITION THE-REPLACEMENT))
  then only the first example, and not the second, will have control transfered 
  to its correction clause.

  Here's a more complete example:

 	(LET ((MY-FOOD 'MILK)
	      (MY-COLOR 'GREENISH-BLUE))
	  (DO ()
	      ((NOT (BAD-FOOD-COLOR-P FOOD COLOR)))
	    (PROCEED-CASE (ERROR 'BAD-FOOD-COLOR :FOOD MY-FOOD :COLOR MY-COLOR)
	      (USE-FOOD  (IGNORE NEW-FOOD)  (SETQ MY-FOOD  NEW-FOOD))
	      (USE-COLOR (IGNORE NEW-COLOR) (SETQ MY-COLOR NEW-COLOR))))
	  ;; We won't get to here until MY-FOOD and MY-COLOR are compatible.
	  (LIST MY-FOOD MY-COLOR))

  A handler can then proceed the error in either of two ways. It may correct
  the color or correct the food. For example:

       #'(LAMBDA (CONDITION) ... (USE-COLOR CONDITION 'WHITE)  ...) ;Corrects color
   or  #'(LAMBDA (CONDITION) ... (USE-FOOD  CONDITION 'CHEESE) ...) ;Corrects food

  Here is an example using CONDITION-BIND and SIGNAL-CASE...

	(CONDITION-BIND ((FOO-ERROR
			   #'(LAMBDA (CONDITION) (USE-VALUE CONDITION 7))))
	  (PROCEED-CASE (ERROR 'FOO-ERROR)
	    (USE-VALUE (IGNORE X) (* X X))))
	=> 49